#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Copyright (c) 61DUke.

# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the Readme Document for more details.
# Create: 2020-12-27

import numpy as np
import exifread
import re
import geocoder

__all__ = ["location_gps"]


class GeosTransformer(object):
    def __init__(self):
        self.x_pi = np.pi * 3000.0 / 180.0
        self.eccentricity = 0.00669342162296594323
        self.si_major = 6378245.0

    @classmethod
    def _transform(cls, matrix):
        """
        纬度转换
        :param matrix:
        :return:
        """
        return np.array([
            np.sum([
                300.0 + matrix[:, 0] + 2.0 * matrix[:, 1] + 0.1 * matrix[:, 0] * matrix[:, 0] +
                0.1 * matrix[:, 0] * matrix[:, 1] + 0.1 * np.sqrt(np.abs(matrix[:, 0])),
                (20.0 * np.sin(6.0 * matrix[:, 0] * np.pi) + 20.0 * np.sin(2.0 * matrix[:, 0] * np.pi)) * 2.0 / 3.0,
                (20.0 * np.sin(matrix[:, 0] * np.pi) + 40.0 * np.sin(matrix[:, 0] / 3.0 * np.pi)) * 2.0 / 3.0,
                (150.0 * np.sin(matrix[:, 0] / 12.0 * np.pi) + 300.0 * np.sin(matrix[:, 0] / 30.0 * np.pi)) * 2.0 / 3.0
            ], axis=0),
            np.sum([
                -100.0 + 2.0 * matrix[:, 0] + 3.0 * matrix[:, 1] + 0.2 * matrix[:, 1] * matrix[:, 1] +
                0.1 * matrix[:, 0] * matrix[:, 1] + 0.2 * np.sqrt(abs(np.abs(matrix[:, 0]))),
                (20.0 * np.sin(6.0 * matrix[:, 0] * np.pi) + 20.0 * np.sin(2.0 * matrix[:, 0] * np.pi)) * 2.0 / 3.0,
                (20.0 * np.sin(matrix[:, 1] * np.pi) + 40.0 * np.sin(matrix[:, 1] / 3.0 * np.pi)) * 2.0 / 3.0,
                (160.0 * np.sin(matrix[:, 1] / 12.0 * np.pi) + 320 * np.sin(matrix[:, 1] * np.pi / 30.0)) * 2.0 / 3.0
            ], axis=0)
        ]).T

    def bd09_to_gcj02(self, bd_matrix):
        """
        百度BD09坐标标准 转 国测局标准坐标
        :param bd_matrix:
        :return:
        """
        if not isinstance(bd_matrix, np.ndarray):
            raise ValueError("matrix parameter is not Numpy type!")
        if not bd_matrix.ndim == 2:
            raise ValueError("matrix parameter must 2-d array")
        x, y, = bd_matrix[:, 0] - 0.0065, bd_matrix[:, 1] - 0.006
        z = (np.sqrt(x * x + y * y)) - (0.00002 * np.sin(y * self.x_pi))
        theta = np.arctan2(y, x) - 0.000003 * np.cos(x * self.x_pi)
        return np.array([
            z * np.cos(theta),
            z * np.sin(theta)
        ]).T

    def gcj02_to_bd09(self, gcj_matrix):
        """
        国测局标准坐标 转 百度BD09坐标标准
        :param gcj_matrix:
        :return:
        """
        if not isinstance(gcj_matrix, np.ndarray):
            raise ValueError("matrix parameter is not Numpy type!")
        if not gcj_matrix.ndim == 2:
            raise ValueError("matrix parameter must 2-d array")
        z = np.sqrt(np.square(gcj_matrix[:, 0]) + np.square(gcj_matrix[:, 1])) + 0.00002 * np.sin(
            gcj_matrix[:, 1] * self.x_pi)
        theta = np.arctan2(gcj_matrix[:, 1], gcj_matrix[:, 0]) + 0.000003 * np.cos(gcj_matrix[:, 0] * self.x_pi)
        return np.array([
            z * np.cos(theta) + 0.0065,
            z * np.sin(theta) + 0.006
        ]).T

    def gcj02_to_wgs84(self, gcj_matrix):
        """
        国测局标准坐标 转 WGS-84标准坐标
        :param gcj_matrix:
        :return:
        """
        if not isinstance(gcj_matrix, np.ndarray):
            raise ValueError("matrix parameter is not Numpy type!")
        if not gcj_matrix.ndim == 2:
            raise ValueError("matrix parameter must 2-d array")
        transform_gcj_matrix = self._transform(
            np.array([
                gcj_matrix[:, 0] - 105.0,
                gcj_matrix[:, 1] - 35.0]
            ).T
        )
        rad_lat = gcj_matrix[:, 1] / 180.0 * np.pi
        magic = 1 - self.eccentricity * np.square(np.sin(rad_lat))
        sqrt_magic = np.sqrt(magic)
        return np.array([
            gcj_matrix[:, 0] - (
                (transform_gcj_matrix[:, 0] * 180.0) / (self.si_major / sqrt_magic * np.cos(rad_lat) * np.pi)),
            gcj_matrix[:, 1] - ((transform_gcj_matrix[:, 1] * 180.0) / (
                (self.si_major * (1 - self.eccentricity)) / (magic * sqrt_magic) * np.pi))
        ]).T

    def wgs84_to_gcj02(self, wgs_matrix):
        """
        WGS-84标准坐标 转 国测局标准坐标
        :param wgs_matrix:
        :return:
        """
        if not isinstance(wgs_matrix, np.ndarray):
            raise ValueError("matrix parameter is not Numpy type!")
        if not wgs_matrix.ndim == 2:
            raise ValueError("matrix parameter must 2-d array")
        transform_wgs_matrix = self._transform(
            np.array([
                wgs_matrix[:, 0] - 105.0,
                wgs_matrix[:, 1] - 35.0]
            ).T
        )
        rad_lat = wgs_matrix[:, 1] / 180.0 * np.pi
        magic = 1.0 - self.eccentricity * np.square(np.sin(rad_lat))
        sqrt_magic = np.sqrt(magic)
        return np.array([
            wgs_matrix[:, 0] + (
                (transform_wgs_matrix[:, 0] * 180.0) / (self.si_major / sqrt_magic * np.cos(rad_lat) * np.pi)),
            wgs_matrix[:, 1] + ((transform_wgs_matrix[:, 1] * 180.0) / (
                (self.si_major * (1 - self.eccentricity)) / (magic * sqrt_magic) * np.pi))
        ]).T

    def bd09_to_wgs84(self, bd_matrix):
        """
        百度BD09标准坐标 转 WGS-84标准坐标
        :param bd_matrix:
        :return:
        """
        if not isinstance(bd_matrix, np.ndarray):
            raise ValueError("matrix parameter is not Numpy type!")
        if not bd_matrix.ndim == 2:
            raise ValueError("matrix parameter must 2-d array")
        return self.gcj02_to_wgs84(
            self.bd09_to_gcj02(bd_matrix)
        )

    def wgs84_to_bd09(self, wgs_matrix):
        """
        WGS-84标准坐标 转 百度BD09标准坐标
        :param wgs_matrix:
        :return:
        """
        if not isinstance(wgs_matrix, np.ndarray):
            raise ValueError("matrix parameter is not Numpy type!")
        if not wgs_matrix.ndim == 2:
            raise ValueError("matrix parameter must 2-d array")
        return self.gcj02_to_bd09(
            self.wgs84_to_gcj02(wgs_matrix)
        )

    @staticmethod
    def wgs84_to_mercator(wgs_matrix):
        """

        :param wgs_matrix:
        :return:
        """
        if not isinstance(wgs_matrix, np.ndarray):
            raise ValueError("matrix parameter is not Numpy type!")
        if not wgs_matrix.ndim == 2:
            raise ValueError("matrix parameter must 2-d array")
        return np.array([
            wgs_matrix[:, 0] * 20037508.342789 / 180.0,
            np.log(np.tan((90.0 + wgs_matrix[:, 1]) * np.pi / 360.0)) / (
                np.pi / 180.0) * 20037508.34789 / 180.0
        ]).T

    @staticmethod
    def mercator_to_wgs84(mer_matrix):
        """

        :param mer_matrix:
        :return:
        """
        if not isinstance(mer_matrix, np.ndarray):
            raise ValueError("matrix parameter is not Numpy type!")
        if not mer_matrix.ndim == 2:
            raise ValueError("matrix parameter must 2-d array")
        return np.array([
            mer_matrix[:, 0] / 20037508.34 * 180.0,
            180.0 / np.pi * (
                2 * np.arctan(np.exp(mer_matrix[:, 1] / 20037508.34 * 180.0 * np.pi / 180.0)) - np.pi / 2.0)
        ]).T

    def bd09_to_mercator(self, bd_matrix):
        """

        :param bd_matrix:
        :return:
        """
        if not isinstance(bd_matrix, np.ndarray):
            raise ValueError("matrix parameter is not Numpy type!")
        if not bd_matrix.ndim == 2:
            raise ValueError("matrix parameter must 2-d array")
        return self.wgs84_to_mercator(
            self.bd09_to_wgs84(bd_matrix)
        )

    def mercator_to_bd09(self, mer_matrix):
        """

        :param mer_matrix:
        :return:
        """
        if not isinstance(mer_matrix, np.ndarray):
            raise ValueError("matrix parameter is not Numpy type!")
        if not mer_matrix.ndim == 2:
            raise ValueError("matrix parameter must 2-d array")
        return self.wgs84_to_bd09(
            self.mercator_to_wgs84(mer_matrix)
        )

    @staticmethod
    def angle_to_decimal(deg, min, sec):
        """
        Latitude and longitude to decimal

        :param deg:
        :param min:
        :param sec:
        :return:
        """
        return float(deg) + ((float(min) + (float(sec.split("/")[0]) / float(sec.split("/")[-1]) / 60)) / 60)


geosTransformer = GeosTransformer()


def location_gps(image_path,
                 gaode_key="db459b3dbc90afaef32dec46d228077e"
                 ):
    """
    Get Image GPS Information

    ExifRead, Python library to extract EXIF data from tiff and jpeg files.

    :param image_path:
    :param gaode_key:
    :return:

    Example:
    ---------
        >>> gps = location_gps(image_path="imgs/IMG_2229.JPG")
        >>> print(gps)
        >>> {
        ...    "GPS": {
        ...       "GPSLatitudeRef": "N",
        ...        "GPSLatitude": 18.292461111111113,
        ...        "GPSLongitudeRef": "E",
        ...        "GPSLongitude": 109.52616944444445,
        ...        "GPSAltitudeRef": "0",
        ...        "GPSAltitude": "41477/8571",
        ...        "GPSAddress": "海南省三亚市吉阳区河东区街道迎宾路329号三亚千古情景区"
        ...    },
        ...    "date": "2019:12:20 14:29:37"
        ... }

    """
    if not isinstance(image_path, str) or not image_path:
        raise ValueError("image_path param must be string!")

    GPS, date = {}, ""
    with open(image_path, "rb") as f:
        tags = exifread.process_file(f)

    # process image original information
    for tag, value in tags.items():
        if re.match("GPS GPSLatitudeRef", tag):
            GPS["GPSLatitudeRef"] = str(value)

        elif re.match("GPS GPSLongitudeRef", tag):
            GPS["GPSLongitudeRef"] = str(value)

        elif re.match("GPS GPSAltitudeRef", tag):
            GPS["GPSAltitudeRef"] = str(value)

        elif re.match("GPS GPSLatitude", tag):
            try:
                match_res = re.match("\[(\w*),(\w*),(\w.*)\]", str(value)).groups()
                GPS["GPSLatitude"] = geosTransformer.angle_to_decimal(*match_res)
            except Exception as e:
                deg, min, sec = [x.replace(" ", "") for x in str(value)[1:-1].split(",")]
                GPS["GPSLatitude"] = geosTransformer.angle_to_decimal(deg, min, sec)

        elif re.match("GPS GPSLongitude", tag):
            try:
                match_res = re.match("\[(\w*),(\w*),(\w.*)\]", str(value)).groups()
                GPS["GPSLongitude"] = geosTransformer.angle_to_decimal(*match_res)
            except Exception as e:
                deg, min, sec = [x.replace(" ", "") for x in str(value)[1:-1].split(",")]
                GPS["GPSLongitude"] = geosTransformer.angle_to_decimal(deg, min, sec)

        elif re.match("GPS GPSAltitude", tag):
            GPS["GPSAltitude"] = str(value)
        elif re.match(".*Date.*", tag):
            date = str(value)

    # gaode location
    if GPS.get("GPSLongitude") and GPS.get("GPSLatitude"):
        GPS["GPSAddress"] = geocoder.gaode(
            geosTransformer.wgs84_to_gcj02(
                wgs_matrix=np.array([[GPS["GPSLongitude"], GPS["GPSLatitude"]]])
            )[0].tolist()[::-1],
            method="reverse",
            key=gaode_key
        ).address

    return {
        "GPS": GPS,
        "date": date
    }


if __name__ == '__main__':
    pass
